package p.ithorns.framework.common.geo;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.springframework.util.CollectionUtils;
import p.ithorns.framework.common.codec.MD5Codec;
import p.ithorns.framework.common.utils.JsonUtil;
import p.ithorns.framework.common.utils.StringUtil;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * GeometryExtUtil
 * 基于JTS的几何扩展工具
 *
 * @author Ht.L
 * @date 2024-01-05 15:03
 * @since 1.0.0
 */
public class GeoUtil {

    static final String DEFAULT_DELIMITER = ",";

    public static class Circle {
        /**
         * 圆心
         */
        private String center;
        /**
         * 半径
         */
        private double radius;

        public String getCenter() {
            return center;
        }

        public void setCenter(String center) {
            this.center = center;
        }

        public double getRadius() {
            return radius;
        }

        public void setRadius(double radius) {
            this.radius = radius;
        }
    }


    // static String coors = "[111.20412623425818,30.684542883101944,0.1136208253163052,111.20396353777745,30.684765397771365,0.17729942004548352,111.20355175625907,30.684608802016914,-0.29593277552909103,111.20373819297507,30.684394237691727,-0.8226553987665423]";

    private final static GeometryFactory FACTORY = new GeometryFactory();

    /**
     * 对位置点进行摘要处理
     *
     * @param str -
     * @return String
     */
    public static String summaryCoordinatesIndex(String str) {
        String target = StringUtil.trimAllWhitespace(str);
        try {
            return MD5Codec.md5(target, "");
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }


    public static Point createPoint(Double lng, Double lat) {
        return FACTORY.createPoint(new Coordinate(lng, lat));
    }

    public static Polygon createPolygon(String coordStr) {
        Coordinate[] coordinates = toCoordinates(coordStr);
        return FACTORY.createPolygon(coordinates);
    }

    public static Polygon createPolygon(Coordinate[] coordinates) {
        return FACTORY.createPolygon(coordinates);
    }

    /**
     * @param coors -坐标数组(1d), 包含高度h
     *              格式[lng1,lat1,h1,lng2,lat2,h2,...]
     * @return Coordinate[]
     */
    private static Coordinate[] toCoordinates(String coors) {
        String plain = StringUtils.trim(coors);
        if (StringUtils.isBlank(plain)) {
            return new Coordinate[0];
        }

        if (!plain.startsWith("[") || !plain.endsWith("]")) {
            throw new IllegalArgumentException("位置数据不符合格式规范: 非'[]'包含");
        }

        // 去除头尾
        plain = plain.substring(1, plain.length() - 1);
        String[] parts = StringUtils.split(plain, ",");
        List<Coordinate> coordinates = new ArrayList<>();
        for (int i = 0; i < parts.length; i += 3) {
            double x = Double.parseDouble(parts[i]);
            double y = Double.parseDouble(parts[i + 1]);
            double z = Double.parseDouble(parts[i + 2]);
            coordinates.add(new Coordinate(x, y, z));
        }

        // 封闭区域
        if (!coordinates.isEmpty()) {
            coordinates.add(coordinates.get(0));
        }
        return coordinates.toArray(Coordinate[]::new);
    }


    /**
     * 计算多边形圆心
     *
     * @param areaCoordinates -坐标点
     * @return Circle
     */
    public static Circle calcCircleCenter(String areaCoordinates) {
        List<List<Double>> areaPolygon = parsePointsFromJson(areaCoordinates);
        Circle circle = new Circle();

        // 计算多边形围栏中心点
        int size = areaPolygon.size();
        double longitudes = 0d;
        double latitudes = 0d;
        for (List<Double> doubles : areaPolygon) {
            longitudes += doubles.get(0);
            latitudes += doubles.get(1);
        }

        double longitude = longitudes / size;
        double latitude = latitudes / size;
        String centerPoint = longitude + DEFAULT_DELIMITER + latitude;
        circle.setCenter(centerPoint);

        // 计算多边形近似圆最大半径
        double maxDistance = 0d;
        for (List<Double> doubles : areaPolygon) {
            double distance = MapUtil.getDistance(doubles.get(0), doubles.get(1), longitude, latitude);
            maxDistance = Math.max(maxDistance, distance);
        }
        circle.setRadius(maxDistance);

        return circle;
    }


    /**
     * 判断多边形是否包含点
     *
     * @param coordinates -多边形坐标集
     * @param lng         - 经度
     * @param lat         - 纬度
     * @return boolean
     */
    public static boolean isPolygonContainsPoint(String coordinates, double lng, double lat) {
        Polygon polygon = GeoUtil.createPolygon(coordinates);
        Point point = GeoUtil.createPoint(lng, lat);
        return polygon.contains(point);
    }


    /**
     * 射线法判断指定多边形是否包含点
     * (点是否在多边形范围内)
     * 包含复杂多边形，顶点相交的情况
     *
     * @param points - 多边形顶点列表
     * @param target - 目标点
     * @return boolean
     */
    public static boolean isPolygonContainsPoint(List<List<Double>> points, List<Double> target) {
        int iCount, iIndex, iSum = 0;
        double dLon1, dLon2, dLat1, dLat2, dLon;

        int vertexSize = points.size();
        if (vertexSize < 3) {
            return false;
        }
        iCount = vertexSize;
        for (iIndex = 0; iIndex < iCount; iIndex++) {
            if (iIndex == iCount - 1) {
                List<Double> p0 = points.get(0);
                dLon2 = p0.get(0);
                dLat2 = p0.get(1);
                List<Double> pi = points.get(iIndex);
                dLon1 = pi.get(0);
                dLat1 = pi.get(1);
            } else {
                List<Double> p0 = points.get(iIndex + 1);
                dLon2 = p0.get(0);
                dLat2 = p0.get(1);
                List<Double> pi = points.get(iIndex);
                dLon1 = pi.get(0);
                dLat1 = pi.get(1);
            }

            double aLon = target.get(0);
            double aLat = target.get(1);

            // 判断A点是否在边的两端点的水平平行线之间，在则可能有交点，开始判断交点是否在左射线上
            if (((aLat >= dLat1) && (aLat < dLat2)) || ((aLat >= dLat2) && (aLat < dLat1))) {
                if (Math.abs(dLat1 - dLat2) > 0) {
                    //得到 A点向左射线与边的交点的x坐标：
                    dLon = dLon1 - ((dLon1 - dLon2) * (dLat1 - aLat)) / (dLat1 - dLat2);
                    // 如果交点在A点左侧（说明是做射线与 边的交点），则射线与边的全部交点数加一：
                    if (dLon < aLon) {
                        iSum++;
                    }
                }
            }
        }
        return (iSum % 2) != 0;
    }


    private static List<List<Double>> parsePointsFromJson(String json) {
        List<Double> points = parsePointsJson(json);
        List<List<Double>> rs = new ArrayList<>();
        for (int i = 0; i < points.size(); i += 3) {
            double x = points.get(i);
            double y = points.get(i + 1);
            rs.add(Lists.newArrayList(x, y));
        }

        return rs;
    }

    /**
     * @param json -坐标数组(1d), 包含高度h
     *             格式[lng1,lat1,h1,lng2,lat2,h2,...]
     * @return Coordinate[]
     */
    public static Coordinate[] parseCoordinatesFromJson(String json) {
        List<Double> points = parsePointsJson(json);

        List<Coordinate> rs = new ArrayList<>();
        for (int i = 0; i < points.size(); i += 3) {
            double x = points.get(i);
            double y = points.get(i + 1);
            double z = points.get(i + 2);
            rs.add(new Coordinate(x, y, z));
        }

        // 封闭
        if (CollectionUtils.isEmpty(rs)) {
            return new Coordinate[0];
        }
        rs.add(rs.get(0));
        return rs.toArray(Coordinate[]::new);
    }

    private static List<Double> parsePointsJson(String json) {
        try {
            return JsonUtil.fromJson(json, new TypeReference<List<Double>>() {
            });
        } catch (Exception var3) {
            return Collections.emptyList();
        }
    }

    public static void main(String[] args) {
        String coordinates = "[111.59173346476321,30.211188567961496,12.392844183271395,111.59302371276343,30.209703104722585,13.777653585089269,111.59482822147183,30.21089068159723,10.235918836785256,111.59355288631771,30.212367949756818,9.203028311657553]";
        boolean contains = GeoUtil.isPolygonContainsPoint(coordinates, 111.5925660177867, 30.210535479346106);
        System.out.println(contains);
    }

}